package com.chrishobson.jreedemo;

import com.chrishobson.jreedemo.utils.Camera;
import com.chrishobson.jreedemo.utils.DemoBase;
import com.chrishobson.jreedemo.utils.DrawList;
import com.chrishobson.jreedemo.utils.SimpleAxes;
import com.chrishobson.jreedemo.utils.glutCube;
import com.chrishobson.jreedemo.utils.glutSphere;
import com.chrishobson.jreeglut.*;
import com.chrishobson.maths.Matrix44;
import com.chrishobson.maths.Vector3D;

import static com.chrishobson.jreeglut.glut.*;
import static com.chrishobson.jreegl.gl.*;

/**
 * First 3d drawing. Cube spins as the window is resized.
 * 
 * 
 * 
 * 
 * @author clh
 * 
 */
public class Second3D extends DemoBase implements DisplayHandler,
    KeyboardHandler, SpecialHandler {

  public Second3D(String[] a_Args) {
    super("3D Second");
    m_W.SetDisplayHandler(this);
    m_W.SetKeyboardHandler(this);
    m_W.SetSpecialHandler(this);

    m_Angles = new Second3DDirWin(this);

    m_Camera.SetLookAt(m_LookAt);
    m_Camera.SetUp(m_Up);

    // m_Drawables.add(m_Camera);
    m_Drawables.add(m_Cube);
    m_Drawables.add(new SimpleAxes(0.55));
  }

  glutWindow GetWindow()
  {
    return m_W;
  }
  
  public void DisplayFunc(DisplayData a_Data) {
    glClearColor(.2, 0, 0, 0);
    glClear(GL_COLOR_BUFFER_BIT);

    glColor(1.0, 1.0, 1.0);

    m_Camera.Draw(null);
    m_Drawables.Draw(null);

    glFlush();
    glutSwapBuffers();
  }

  /**
   * Returns a unit vector which points at the AZ/EL away from the centre of a
   * sphere to the edge.
   * 
   * @param a_Vector
   *          The vector
   */

  void GetAZEL(Vector3D a_Vec) {
    double l_EL = Math.toRadians(m_EL);
    double l_AZ = Math.toRadians(m_AZ);

    /**
     * The vector is generated by first applying a rotation around Y by the
     * elevation to a vector 1,0,0 and the around Z by the azimuth. The right
     * handed matrices are concatted and multipled out by 1,0,0 to give:-
     */
    a_Vec.SetIJK(Math.cos(l_AZ) * Math.cos(l_EL),
        Math.sin(l_AZ) * Math.cos(l_EL), Math.sin(l_EL));
  }

  Vector3D GetLookAt() {
    return m_LookAt;
  }

  Vector3D GetUp() {
    return m_Up;
  }

  public void KeyboardFunc(KeyboardData a_Data) {
    if (a_Data.GetKey() == 27) {
      // ESC is exit, probably should exit glut main loop and drop
      // out normally from main though
      // TODO leave main loop
      System.exit(0);
    } else if (a_Data.GetKey() == 110) { // n
      m_Camera.SetNear(m_Camera.GetNear() - .1);
    } else if (a_Data.GetKey() == 78) { // N
      m_Camera.SetNear(m_Camera.GetNear() + .1);
    } else if (a_Data.GetKey() == 102) { // f
      m_Camera.SetFar(m_Camera.GetFar() + .1);
    } else if (a_Data.GetKey() == 70) { // F
      m_Camera.SetFar(m_Camera.GetFar() - .1);
    } else {
      System.out.println("key = " + a_Data.GetKey());
    }
    a_Data.GetWindow().PostRedisplay();
  }

  public void SpecialFunc(KeyboardData a_Data) {
    if (a_Data.GetKey() == glut.GLUT_KEY_LEFT) {
      if (a_Data.IsShift()) {
        RotateUp(-1);
      } else {
        SetAZEL(m_AZ - 1, m_EL);
      }
    } else if (a_Data.GetKey() == glut.GLUT_KEY_RIGHT) {
      if (a_Data.IsShift()) {
        RotateUp(1);
      } else {
        SetAZEL(m_AZ + 1, m_EL);
      }
    } else if (a_Data.GetKey() == glut.GLUT_KEY_UP) {
      SetAZEL(m_AZ, m_EL + 1);
    } else if (a_Data.GetKey() == glut.GLUT_KEY_DOWN) {
      SetAZEL(m_AZ, m_EL - 1);
    }
    a_Data.GetWindow().PostRedisplay();
    m_Angles.Update();
  }

  private void SetAZEL(double l_AZ, double l_EL) {
    m_AZ = l_AZ;
    m_EL = l_EL;

    GetAZEL(m_LookAt);
    m_Up.MakePerp(m_LookAt);
    m_Camera.SetLookAt(m_LookAt);
    m_Camera.SetUp(m_Up);
  }

  public void RotateUp(double angle) {
    m_Mat.make_rotation_vector(m_LookAt, angle);
    m_Mat.transform(m_Up, m_Up);
    m_Camera.SetUp(m_Up);
  }

  DrawList GetDrawing()
  {
    return m_Drawables;
  }
  
  double m_AZ = 0.0;
  double m_EL = 0;
  Vector3D m_LookAt = new Vector3D(1, 0, 0);
  Vector3D m_Up = new Vector3D(0, 0, 1);
  Second3DDirWin m_Angles;
  private Matrix44 m_Mat = new Matrix44();

  private Camera m_Camera = new Camera();
  private glutCube m_Cube = new glutCube(1.0);
  private DrawList m_Drawables = new DrawList();
}

/**
 * This class provides and extra window which shows and Isometric view and shows
 * the orientation of the gluLookAt in the main view. As the main view is
 * rotated this view updates to show the lookat direction and the up vector of
 * the main view.
 * 
 * @author clh
 * 
 */
class Second3DDirWin extends DemoBase implements DisplayHandler {
  Second3DDirWin(Second3D a_3D) {
    super("View Dir");

    m_W.SetDisplayHandler(this);
    m_3D = a_3D;
    m_Camera.SetLookAt(new Vector3D(-1, -1, 1));
    m_Camera.SetUp(new Vector3D(0, 0, 1));

    m_Camera.SetLeft(-1.1);
    m_Camera.SetRight(1.1);
    m_Camera.SetBottom(-1.1);
    m_Camera.SetTop(1.1);
    m_Camera.SetNear(-2);
    m_Camera.SetFar(3);

    m_Drawables.add(m_Camera);
    m_Drawables.add(new glutSphere(1, 20, 20));
    m_Drawables.add(new SimpleAxes(1.0));
    m_Drawables.add(a_3D.GetDrawing());
  }

  public void Update() {
    m_W.PostRedisplay();
  }

  public void DisplayFunc(DisplayData a_Data) {
    if(m_FirstDraw) {
      m_W.ShareLists(m_3D.GetWindow());
      m_FirstDraw = false;
    }
    
    glClearColor(0, 0, 0, 0);
    glClear(GL_COLOR_BUFFER_BIT);
    Vector3D l_LookAt = m_3D.GetLookAt();
    Vector3D l_Up = m_3D.GetUp();

    m_Drawables.Draw(null);

    glColor(0, 1, 1);
    glBegin(GL_LINE_STRIP);
    {
      glVertex(0, 0, 0);
      glVertex(l_LookAt.GetI(), l_LookAt.GetJ(), l_LookAt.GetK());
      glColor(1, 1, 1);
      glVertex(l_LookAt.GetI() + l_Up.GetI(), l_LookAt.GetJ() + l_Up.GetJ(),
          l_LookAt.GetK() + l_Up.GetK());
    }
    glEnd();

    glFlush();
    glutSwapBuffers();
  }

  private Second3D m_3D;
  private DrawList m_Drawables = new DrawList();
  private Camera m_Camera = new Camera();
  private boolean m_FirstDraw = true;
}
